﻿using System.Collections.Generic;

using SwyishChess.Domain.Board;
using SwyishChess.Domain.Piece;
using SwyishChess.Domain.Player;
using SwyishChess.Domain.Move;

namespace SwyishChess.Service
{
    public class MoveValidationService : IMoveValidationService
    {
        private const int CHESS_BOARD_ROW = 8;
        private const int CHESS_BOARD_COLUMN = 8;
        private const int CHESS_BOARD_TOP_ROW = 0;
        private const int CHESS_BOARD_BOTTOM_ROW = 8;
        private const int CHESS_BOARD_LEFT_COLUMN = 0;
        private const int CHESS_BOARD_RIGHT_COLUMN = 8;

        private const bool AVAILABLE_FOR_MOVE = true;

        private const int BLACK_PLAYER = 2;
        private const int WHITE_PLAYER = 1;

        /// <summary>
        /// Checks if a poassible move is a valid one
        /// </summary>
        /// <param name="ChessPieces">ChessPieces array</param>
        /// <param name="cp">The current chess piece we are checking the possible moves on</param>
        /// <param name="currentRow">The current row location of the piece</param>
        /// <param name="currentColumn">The current column location of the piece</param>
        /// <param name="newRow">The new row location of the piece</param>
        /// <param name="newColumn">The new column locatinon of the piece</param>
        /// <returns>Boolean value indicating whether the move is valid or not</returns>
        public bool ValidateMove(ChessPiece[,] chessPieces, ChessPiece cp,
            int currentRow, int currentColumn, int newRow, int newColumn)
        {
            bool isValid = true;

            if (cp is Pawn)
            {
                // Pawn specific checks
                isValid = ValidateMoveForPawn(chessPieces, cp, currentRow, currentColumn, newRow, newColumn);
            }
            else if (cp is Rook || cp is Queen || cp is Bishop)
            {
                isValid = ValidMoveForRookQueenBishop(chessPieces, cp, currentRow, currentColumn, newRow, newColumn);
            }
            else if (cp is King)
            {
                // King specific checks
                isValid = ValidateMoveForKing(chessPieces, cp, currentRow, currentColumn, newRow, newColumn);
            }

            //only if valid continue with next check if not its not then there is no need to check the following.
            if (isValid)
            {
                // Retruns false if a possible move contains the player's own chess piece
                if (chessPieces[newRow, newColumn] != null)
                    if (cp.OwningPlayerId == chessPieces[newRow, newColumn].OwningPlayerId)
                        isValid = false;
            }
            return isValid;
        }

        /// <summary>
        /// Validate move for king
        /// </summary>
        /// <param name="ChessPieces"></param>
        /// <param name="cp"></param>
        /// <param name="currentRow"></param>
        /// <param name="currentColumn"></param>
        /// <param name="newRow"></param>
        /// <param name="newColumn"></param>
        /// <returns></returns>
        private bool ValidateMoveForKing(ChessPiece[,] chessPieces, ChessPiece cp,
            int currentRow, int currentColumn, int newRow, int newColumn)
        {
            bool isValid = true;
            bool isEligibleForCastling = false;

            // Only checks for castling if the King is at original position && the row for which
            //isvalid is asked is the same row because if its a different row then there is no eligibility for castling.
            if ((currentRow == 0 || currentRow == 7) && currentColumn == 4 && currentRow == newRow)
            {
                isEligibleForCastling = CheckForCastling(chessPieces, cp);
            }

            // If king is eligible for castling
            if (isEligibleForCastling)
            {
                //to see whether right or left of the king.
                //in this case it is right side
                if (newColumn > 4)
                {
                    //check the other things only if the box is not adjacent
                    //i.e difference b/w the column number tells whether its adjacent or not.
                    if (newColumn - currentColumn > 1)
                    {
                        //check and if there is any pieces between king and rook or 
                        //if the rook has moved already then the box is not valid for movement
                        for (int j = 5; j < 7; j++)
                        {
                            if ((chessPieces[newRow, 7] != null && chessPieces[newRow, 7].MoveCount > 0) || (chessPieces[newRow, j] != null))
                            {
                                isValid = false;
                                break;
                            }

                        }
                    }
                }
                //left side
                else
                {
                    //check the other things only if the box is not adjacent
                    //i.e difference b/w the column number tells whether its adjacent or not.
                    if (currentColumn - newColumn > 1)
                    {
                        //check and if there is any pieces between king and rook or 
                        //if the rook has moved already then the box is not valid for movement
                        for (int j = 3; j > 0 ; j--)
                        {
                            if ((chessPieces[newRow, 0] != null && chessPieces[newRow, 0].MoveCount > 0) || (chessPieces[newRow, j] != null))
                            {
                                isValid = false;
                                break;
                            }

                        }
                    }
                }               
            }
            else
            {
                //Remove if its other than the adjacent box
                if ((currentColumn - newColumn) > 1 || (newColumn - currentColumn) > 1)
                {
                    isValid = false;
                }
            }

            return isValid;
        }

        /// <summary>
        /// validate move for rook queen and bishop
        /// </summary>
        /// <param name="ChessPieces"></param>
        /// <param name="cp"></param>
        /// <param name="currentRow"></param>
        /// <param name="currentColumn"></param>
        /// <param name="newRow"></param>
        /// <param name="newColumn"></param>
        /// <returns></returns>
        private bool ValidMoveForRookQueenBishop(ChessPiece[,] chessPieces, ChessPiece cp,
            int currentRow, int currentColumn, int newRow, int newColumn)
        {
            bool isValid = true;

            // Checks vertical movements
            for (int index = currentRow + 1; index < newRow; index++)
            {
                if (currentColumn == newColumn)
                    if (chessPieces[index, newColumn] != null)
                        isValid = false;
            }

            for (int index = currentRow - 1; index > newRow; index--)
            {
                if (currentColumn == newColumn)
                    if (chessPieces[index, newColumn] != null)
                        isValid = false;
            }

            // Checks horizontal movements
            for (int index = currentColumn + 1; index < newColumn; index++)
            {
                if (newRow == currentRow)
                    if (chessPieces[newRow, index] != null)
                        isValid = false;
            }

            for (int index = currentColumn - 1; index > newColumn; index--)
            {
                if (newRow == currentRow)
                    if (chessPieces[newRow, index] != null)
                        isValid = false;
            }

            isValid = CheckDiagonalMoves(chessPieces, currentRow, currentColumn, newRow, newColumn, isValid);

            return isValid;
        }

        /// <summary>
        /// Checks diagonal moves
        /// </summary>
        /// <param name="ChessPieces"></param>
        /// <param name="currentRow"></param>
        /// <param name="currentColumn"></param>
        /// <param name="newRow"></param>
        /// <param name="newColumn"></param>
        /// <param name="isValid"></param>
        /// <returns></returns>
        private bool CheckDiagonalMoves(ChessPiece[,] chessPieces,
            int currentRow, int currentColumn, int newRow, int newColumn, bool isValid)
        {
            // Start checking diagonals
            int rowIndex = currentRow + 1;
            int colIndex = currentColumn + 1;

            while (rowIndex < newRow && colIndex < newColumn)
            {
                if (chessPieces[rowIndex, colIndex] != null)
                    isValid = false;

                rowIndex++;
                colIndex++;
            }

            rowIndex = currentRow - 1;
            colIndex = currentColumn - 1;

            while (rowIndex > newRow && colIndex > newColumn)
            {
                if (chessPieces[rowIndex, colIndex] != null)
                    isValid = false;

                rowIndex--;
                colIndex--;
            }

            rowIndex = currentRow - 1;
            colIndex = currentColumn + 1;

            while (rowIndex > newRow && colIndex < newColumn)
            {
                if (chessPieces[rowIndex, colIndex] != null)
                    isValid = false;

                rowIndex--;
                colIndex++;
            }

            rowIndex = currentRow + 1;
            colIndex = currentColumn - 1;

            while (rowIndex < newRow && colIndex > newColumn)
            {
                if (chessPieces[rowIndex, colIndex] != null)
                    isValid = false;

                rowIndex++;
                colIndex--;
            }
            return isValid;
        }

        /// <summary>
        ///  Pawn specific valid move checks
        /// </summary>
        /// <param name="ChessPieces">The chess piece array</param>
        /// <param name="cp">The chess piece we are validating the mvoes on</param>
        /// <param name="currentRow">The row location the chess piece is moving from</param>
        /// <param name="currentColumn">The column location the hcess piece is moving from</param>
        /// <param name="newRow">The row locatino the chess piece is moving to</param>
        /// <param name="newColumn">The column location the chess piec eis moving to</param>
        /// <returns></returns>
        private bool ValidateMoveForPawn(ChessPiece[,] chessPieces, ChessPiece cp,
            int currentRow, int currentColumn, int newRow, int newColumn)
        {
            bool isValid = true;

            // If pawn is on black's side, he can't move toward black or If pawn is on white's side, he can't move toward white
            if ((cp.OwningPlayerId == 2 && newRow - currentRow < 0) || (cp.OwningPlayerId == 1 && (currentRow - newRow < 0)))
            {
                isValid = false;
            }

            if (isValid)
            {
                // If it is not the initial point, set false to all 2 row jumps
                if (currentRow != 1 && currentRow != 6)
                {
                    if (currentRow - newRow == 2 || newRow - currentRow == 2)
                        isValid = false;
                }
                //if its initial point then check whether the next veritial move has any item already placed.
                else
                {
                    //consider the curent row of the pawn who has never been moved from initial position is 6 (7-1)
                    //and checking whether it can move 2 places (i.e 6-2 = 4) to the row number 4 , then we have to check
                    //whether the row between 6 & 4 i.e 5 is occurpied or not. if occupied then we dont allow the move
                    //so set isValid = false; Also another thing we have to keep in mind is 
                    //when pawn moving vertically there is no column change so we just make sure that 
                    //both new and old column value is same
                    if ((currentRow + newRow) % 2 == 0 && (currentColumn == newColumn))
                    {
                        if (chessPieces[(currentRow + newRow) / 2, currentColumn] != null)
                        {
                            isValid = false;
                        }
                    }
                }
            }

            if (isValid)
            {

                // Checks if possible move is occupied or not
                if (chessPieces[newRow, newColumn] == null)
                {
                    // If diagonal block is not occupied, then pawn cannot move
                    if (newColumn != currentColumn)
                    {
                        isValid = CheckForEnPassant(chessPieces, cp, currentRow, currentColumn, newColumn, isValid);

                    }
                }
                else
                    // If there is somethign in front of the pawn, he cannot move forward
                    if (newColumn == currentColumn)
                        if (chessPieces[newRow, newColumn] != null)
                            isValid = false;
            }
            return isValid;
        }

        /// <summary>
        /// Set all pawns ineligible for en passant
        /// </summary>
        /// <param name="ChessPieces">The chess piece array</param>
        public void DisableEligibilityForEnPassant(ChessPiece[,] ChessPieces)
        {
            for (int row = 0; row < CHESS_BOARD_ROW; row++)
            {
                for (int column = 0; column < CHESS_BOARD_COLUMN; column++)
                {
                    if (ChessPieces[row, column] is Pawn)
                    {
                        ((Pawn)ChessPieces[row, column]).WasLastMove = false;
                    }
                }
            }
        }


        private bool CheckForEnPassant(ChessPiece[,] ChessPieces, ChessPiece cp,
            int currentRow, int currentColumn, int newColumn, bool isValid)
        {
            //* Checking for En Passant....
            // First of all, make sure black pawn is at row 4 and whilte pawn is at row 3
            if ((cp.OwningPlayerId == BLACK_PLAYER && currentRow == 4)
                || (cp.OwningPlayerId == WHITE_PLAYER && currentRow == 3))
            {
                // * Preventing index out of range exception */
                if (currentColumn - 1 >= 0 && currentColumn + 1 <= 7)
                {
                    // Checking the left side first
                    // The left side of the current pawn must not be empty
                    if (ChessPieces[currentRow, currentColumn - 1] != null && // must not be of the same color
                        ChessPieces[currentRow, currentColumn - 1].OwningPlayerId != cp.OwningPlayerId &&  // must be at the same column
                        currentColumn - 1 == newColumn)
                    {
                        if (ChessPieces[currentRow, currentColumn - 1] is Pawn)
                        {
                            if (((Pawn)ChessPieces[currentRow, currentColumn - 1]).MoveCount == 1)
                            {
                                if (((Pawn)ChessPieces[currentRow, currentColumn - 1]).WasLastMove == true)
                                {

                                }
                                else
                                    isValid = false;
                            }
                            else
                                isValid = false;
                        }
                        else
                            isValid = false;
                    }
                    // Checking the right side next with same checks
                    else if (ChessPieces[currentRow, currentColumn + 1] != null &&
                        ChessPieces[currentRow, currentColumn + 1].OwningPlayerId != cp.OwningPlayerId &&
                        currentColumn + 1 == newColumn)
                    {
                        if (ChessPieces[currentRow, currentColumn + 1] is Pawn)
                        {
                            if (((Pawn)ChessPieces[currentRow, currentColumn + 1]).MoveCount == 1)
                            {
                                if (((Pawn)ChessPieces[currentRow, currentColumn + 1]).WasLastMove == true)
                                {

                                }
                                else
                                    isValid = false;
                            }
                            else
                                isValid = false;
                        }
                        else
                            isValid = false;
                    }
                    // If any of above statements fails, set it to false.
                    else
                        isValid = false;
                }
                else
                {
                    isValid = false;
                }
            }
            else
            {
                isValid = false;
            }

            return isValid;
        }

        /// <summary>
        /// Checks whether it is check or not
        /// </summary>
        /// <param name="ChessPieces"></param>
        /// <param name="ChessBoard"></param>
        /// <param name="checkedPlayerID"></param>
        /// <returns></returns>
        public bool CheckForCheck(ChessPiece[,] ChessPieces, ChessBlock[,] ChessBoard, out int checkedPlayerID)
        {
            bool isCheck = false;
            checkedPlayerID = 0;

            // Loops through all pieces
            for (int row = 0; row < CHESS_BOARD_ROW; row++)
            {
                for (int column = 0; column < CHESS_BOARD_COLUMN; column++)
                {
                    if (ChessPieces[row, column] != null)
                    {
                        // Get movable range of each pieces
                        FindMovableChessBlock(ChessPieces, ChessBoard, ChessPieces[row, column], row, column);

                        // loop through the chess piece again
                        for (int row2 = 0; row2 < CHESS_BOARD_ROW; row2++)
                        {
                            for (int column2 = 0; column2 < CHESS_BOARD_COLUMN; column2++)
                            {
                                // Check if there is block that is available for move
                                if (ChessBoard[row2, column2].IsAvailableForMove == true)
                                {
                                    // and if it is occupied
                                    if (ChessPieces[row2, column2] != null)
                                    {
                                        // and if it is by a king
                                        if (ChessPieces[row2, column2] is King)
                                        {
                                            if (ChessPieces[row, column].OwningPlayerId != ChessPieces[row2, column2].OwningPlayerId)
                                            {
                                                checkedPlayerID = ChessPieces[row2, column2].OwningPlayerId;
                                                //then is Check
                                                isCheck = true;
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }

            return isCheck;
        }

        /// <summary>
        /// Looks for chess blocks that the selected chess piece is able to move to
        /// </summary>
        /// <param name="ChessPieces">ChesPieces array</param>
        /// <param name="GameBoard">The game board array</param>
        /// <param name="cp">Selected chess piece</param>
        /// <param name="row">The row location of the chess piece</param>
        /// <param name="column">The column location of the chess piece</param>
        public void FindMovableChessBlock(ChessPiece[,] ChessPieces, ChessBlock[,] GameBoard, ChessPiece cp, int row, int column)
        {
            // Gets all possible moves of the selected chess piece
            List<int[,]> possibleMoves = cp.AllPossibleMoves;

            // Goes through all possible mvoes
            for (int i = 0; i < possibleMoves.Count; i++)
            {
                for (int moveCount = 0; moveCount < possibleMoves[i].GetLength(0); moveCount++)
                {
                    // Exclude all possible moves that is out of bound of the game board
                    if ((row + ((int[,])possibleMoves[i])[moveCount, 0] >= CHESS_BOARD_TOP_ROW
                        && row + ((int[,])possibleMoves[i])[moveCount, 0] < CHESS_BOARD_BOTTOM_ROW)
                        && (column + ((int[,])possibleMoves[i])[moveCount, 1] >= CHESS_BOARD_LEFT_COLUMN
                        && column + ((int[,])possibleMoves[i])[moveCount, 1] < CHESS_BOARD_RIGHT_COLUMN))
                    {
                        // Then check other rules
                        if (ValidateMove(ChessPieces, cp, row, column, ((int[,])possibleMoves[i])[moveCount, 0] + row,
                            ((int[,])possibleMoves[i])[moveCount, 1] + column) == true)
                        {
                            // Only possible mvoes that has passed all the tests are marked available for move
                            GameBoard[((int[,])possibleMoves[i])[moveCount, 0] + row,
                                ((int[,])possibleMoves[i])[moveCount, 1] + column].IsAvailableForMove = AVAILABLE_FOR_MOVE;
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Checks if a pawn is eligible for a promotion
        /// </summary>
        /// <param name="ChessPieces">Chess piecs array</param>
        /// <param name="cp">Just moved chess piece</param>
        /// <returns>Boolean value indicating whether it is eligible for promotion or not</returns>
        public bool CheckForPawnPromotion(ChessPiece[,] chessPieces, ChessPiece cp)
        {
            if (cp is Pawn)
            {
                for (int row = 0; row < CHESS_BOARD_ROW; row += 7)
                {
                    for (int col = 0; col < CHESS_BOARD_COLUMN; col++)
                    {
                        if (chessPieces[row, col] == cp)
                        {
                            return true;
                        }
                    }
                }
            }

            return false;
        }

        /// <summary>
        /// Checks if Castling is possible
        /// </summary>
        /// <param name="ChessPieces">Chess Pieces array</param>
        /// <param name="cp">The chess piece that are checked of eligibility on</param>
        /// <returns>Boolean value indicating whether it is eiligible for castling or not</returns>
        public bool CheckForCastling(ChessPiece[,] chessPieces, ChessPiece cp)
        {
            bool isEligible = false;

            if (cp is King)
            {
                // Check if the king has not moved
                if (((King)cp).MoveCount == 0)
                {
                    int row = 0;

                    while (row < CHESS_BOARD_ROW)
                    {
                        if (chessPieces[row, 4] == cp)
                        {
                            break;
                        }
                        row += 7;
                    }
                    /* Check rooks in the same row as the king
                     * If they also has not moved, set isEligible to true */
                    if ((chessPieces[row, 0] != null && chessPieces[row, 0] is Rook && ((Rook)chessPieces[row, 0]).MoveCount == 0) ||
                        (chessPieces[row, 7] != null && chessPieces[row, 7] is Rook && ((Rook)chessPieces[row, 7]).MoveCount == 0))
                    {
                        isEligible = true;
                    }

                }
            }

            return isEligible;
        }

        /// <summary>
        /// Checks if the last move was en passant
        /// </summary>
        /// <param name="chessPieces">chess pieces array</param>
        /// <param name="cp">moved chess piece</param>
        /// <returns>boolean value indicating whether it was en passant or not</returns>
        public bool CheckIfLastMoveWasEnPassant(ChessPiece[,] chessPieces, ChessPiece cp)
        {
            for (int row = 0; row < CHESS_BOARD_ROW; row++)
            {
                for (int col = 0; col < CHESS_BOARD_COLUMN; col++)
                {
                    if (chessPieces[row, col] == cp)
                    {
                        // Returns true if there is a pawn behind the moved pawn
                        if (cp.OwningPlayerId == BLACK_PLAYER)
                        {
                            if (chessPieces[row - 1, col] is Pawn)
                            {
                                // Only if it just has moved to row 5
                                if (row == 5)
                                {
                                    return true;
                                }
                            }
                        }
                        else
                        {
                            if (chessPieces[row + 1, col] is Pawn)
                            {
                                // Only if it just has moved to row 2
                                if (row == 2)
                                {
                                    return true;
                                }
                            }
                        }
                    }
                }
            }

            return false;
        }

        /// <summary>
        /// Checks if it is game over by counting the number of kings
        /// </summary>
        /// <param name="ChessPieces">Chess pieces array</param>
        /// <returns>boolean value indicating whether it is game over or not</returns>
        public bool CheckForGameOver(ChessPiece[,] chessPieces)
        {
            bool isOver = true;
            int kingCounter = 0;

            for (int row = 0; row < CHESS_BOARD_ROW; row++)
            {
                for (int col = 0; col < CHESS_BOARD_COLUMN; col++)
                {
                    if (chessPieces[row, col] != null)
                    {
                        if (chessPieces[row, col] is King)
                        {
                            kingCounter++;
                        }
                    }
                }
            }

            if (kingCounter == 2)
            {
                isOver = false;
            }

            return isOver;
        }

        /// <summary>
        /// Disables a move if it causes a check to be thrown and return how many available moves are there
        /// </summary>
        /// <param name="row">The original piece row location</param>
        /// <param name="column">The original column location</param>
        /// <param name="chessBlock">Game board</param>
        /// <param name="cp">Chess piece</param>
        /// <returns>Number of moves counter</returns>
        public int RemoveCheckCausingMoves(ChessPiecesModel cpm, ChessBoardModel cbm, int currentTurn, int row, int column, ChessBlock[,] chessBlock, ChessPiece cp)
        {
            int playerID;
            int count = 0;

            // first create a temp copy of the chesspieces array 
            ChessPiece[,] copied = cpm.CreateCopiedChessPiecesArray();
            ChessBlock[,] copiedBoard = cbm.CreateCopiedChessBoard();

            // Then search for a chess block that is marked to move
            for (int row2 = 0; row2 < CHESS_BOARD_ROW; row2++)
            {
                for (int col2 = 0; col2 < CHESS_BOARD_COLUMN; col2++)
                {
                    // If the block is marked to move.
                    if (copiedBoard[row2, col2].IsAvailableForMove == true)
                    {
                        count++;
                        // store w/e was in the movable grid
                        ChessPiece temp = copied[row2, col2];
                        // and store currently selected piece to the movable block
                        copied[row2, col2] = cp;
                        // remove the chess piece from the original location
                        copied[row, column] = null;

                        // reset the available for move
                        for (int y = 0; y < CHESS_BOARD_ROW; y++)
                        {
                            for (int x = 0; x < CHESS_BOARD_COLUMN; x++)
                            {
                                copiedBoard[y, x].IsAvailableForMove = false;
                            }
                        }

                        // now checks if this will declare a check
                        if (CheckForCheck(copied, copiedBoard, out playerID) == true)
                        {
                            // if declared check is on the same color as the moved check
                            if (copied[row2, col2].OwningPlayerId == playerID)
                            {
                                // To prevent it from marking available for move to true
                                // if this move declares check on another player but will also declare check on itself
                                if (currentTurn != copied[row2, col2].OwningPlayerId)
                                { }
                                else
                                {
                                    // Set the move as not a valid move on real board
                                    chessBlock[row2, col2].IsAvailableForMove = false;
                                    count--;
                                }
                            }
                        }

                        // Resets the board
                        copied = cpm.CreateCopiedChessPiecesArray();
                        copiedBoard = cbm.CreateCopiedChessBoard();
                    }
                }
            }

            cp.PossibleMoveCount = count;
            return count;
        }
    }
}
